package org.sradon.mySpring.utils;

import org.apache.commons.beanutils.BeanUtils;
import org.sradon.mySpring.anno.di.Primary;
import org.sradon.mySpring.anno.di.Qualifier;
import org.sradon.mySpring.anno.di.Value;
import org.sradon.mySpring.anno.ioc.PostConstruct;
import org.sradon.mySpring.context.BeanDefine;
import org.sradon.mySpring.model.IocExecutor;

import javax.annotation.Resource;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;

public class AnnoUtils {
    /**
     * 递归查找指定注解
     *     检查类是否有指定注解,
     *     没有? 再检查注解中是否有指定注解
     *     直到注解内没有注解
     * @param clazz
     * @param anno
     * @return
     */
    public static boolean isComponent(Class<?> clazz, Class<? extends Annotation> anno) {
        /**
         *  查找注解的缓存
         */
        HashMap<Class, Object> cache = new HashMap<>();

        // 1. 检查缓存中是否有对应的结果
        if (cache.containsKey(clazz)) return (boolean) cache.get(clazz);

        // 2. 获取该类所有注解
        Annotation[] annos = clazz.getDeclaredAnnotations();

        // 3. 先把对象放入缓存中避免重复检查(设置为F是因为下次再遍历到这个对象就直接返回了, 否则无限递归)
        cache.put(clazz, false);

        // 4. 遍历注解
        // 4.1 当前类没有注解
        if (annos == null || annos.length == 0) return false;
        // 4.2 当前类有注解, 遍历所有注解
        for (Annotation a : annos) {
            // 4.3a 当前注解是目标注解
            if (a.annotationType().equals(anno)) {
                // 返回前写入缓存, 覆盖之前的false
                cache.put(clazz, true);
                return true;
            }
            // 4.3b 不是注解? 看看注解中有没有注解
            else
                return isComponent(a.annotationType(), anno);
        }
        return false;
    }


    /// IOC
    /**
     * 判断类是否被IOC注解修饰
     * @param clazz 字节码对象
     * @return 如果有IOC注解则返回注解, 没有则返回NULL (用于后续读取IOC注解中的Value)
     */
    public static Class<? extends Annotation> isComponent(Class<?> clazz) {
        // 1. 获取该类所有注解
        Annotation[] annos = clazz.getDeclaredAnnotations();

        // 2. 遍历注解
        // 2.2 当前类有注解, 遍历所有注解
        for (Annotation a : annos) {
            for (Class<? extends Annotation> ioc_a : IocExecutor.IOC_ANNOS)
                if (a.annotationType().equals(ioc_a))
                    return ioc_a;
        }
        return null;
    }

    /**
     * 执行PostConstruct注解修饰的方法
     * @param clz  类
     * @param obj 该类实例化的对象
     */
    public static void useInit(Class<?> clz, Object obj) {
        Method[] methods = clz.getDeclaredMethods();
        for (Method m : methods) {
            if (m.isAnnotationPresent(PostConstruct.class)) {
                try {
                    m.invoke(obj);
                } catch (Exception e) {
                    System.out.println("[ERR @ioc @" + obj.getClass().getName()  + "] PostConstruct Failed");
                    e.printStackTrace();
                }
            }
        }
    }



    /// DI
    /**
     * 注入被管理的对象 Autowired按类型查找
     * @param obj
     * @param beans
     */
    public static void putAutowired(Object obj, Field f, Map<String, Object> beans, List<BeanDefine> defines) {
        try {
            // 这里不能用BeanUtils.copyProperty, 因为BeanUtils.copyProperty没有setter无法赋值
            // 0. 首先首先判断有没有被Qualifier修饰指定名称
            if (f.isAnnotationPresent(Qualifier.class)) {
                ReflectUtils.FieldSetter(
                        f,
                        obj,
                        beans.get(f.getAnnotation(Qualifier.class).value())
                );
                return; // 赋值完就不执行下面的了
            }
            // 1. 首先按照类型去容器里面找
            Object bean = beans.get(f.getType().getName());
            // 1.1 找到就注入Bean
            if (bean != null) {
                ReflectUtils.FieldSetter(f, obj, bean);
            }
            // 2. 如果没找到就按照接口去定义对象里面匹配实现类
            else {
                // 2.1 先从容器里面去取
                bean = beans.get(findInterface(defines, f,  beans));
                // 2.2 容器中没找到
                if (bean == null) throw new RuntimeException("[ERR @di] Autowired Failed {" + f.getName() + "} NOT FOUND");
                // 2.3 找到就注入Bean
                ReflectUtils.FieldSetter(f, obj, bean);
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException
                    ("[ERR @di @" + obj.getClass().getName() + "] Autowired Failed, " +
                            "{" + f.getType().getName() + "} NOT FOUND");
        }
    }

    /**
     * 注入被管理的对象 Resource按名称查找, 查不到再按类型查找
     * @param obj
     * @param beans
     */
    public static void putResource(Object obj, Field f, Map beans, List<BeanDefine> defines) {
        // 准备好要注入的对象
        Object bean = null;
        try {
            /// 1. 如果指定了name或type
            // 1.1 如果有指定名称
            String putBeanName = f.getAnnotation(Resource.class).name();
            if (!Objects.equals(putBeanName, "")) {
                // 1.1.1 从容器中获取对象
                bean = beans.get(putBeanName);
                // 1.1.2 没找到
                if (bean == null) throw new RuntimeException("[ERR @di] Bean NOT FOUND");
                // 1.1.3 找到就注入Bean
                ReflectUtils.FieldSetter(f, obj, bean);
                return;     // 赋值完后退出
            }
            // 1.2 如果有指定类型
            Class<?> putBeanType = f.getAnnotation(Resource.class).type();
            if (!Objects.equals(putBeanType, java.lang.Object.class)) {
                // 1.2.1 从容器中获取对象
                bean = beans.get(putBeanType.getName());
                // 1.2.2 没找到
                if (bean == null) {
                    // *1.2.2.1 如果目标被按自定义名称注入, 这里按照类型肯定找不到, 需要去定义对象里面去遍历类型
                    for (BeanDefine define : defines) {
                        if (define.getClazz().equals(putBeanType)) {
                            bean = beans.get(define.getContextKey());
                        }
                    }
                    // *1.2.2.2 还是找不到就抛异常了
                    if (bean == null) throw new RuntimeException("[ERR @di] Bean NOT FOUND");
                }
                // 1.2.3 找到就注入Bean
                ReflectUtils.FieldSetter(f, obj, bean);
                return;
            }
            /// 2. 没有指定
            // 2.1 先按照名称查找
            bean = beans.get(f.getName());
            if (bean != null) {
                ReflectUtils.FieldSetter(f, obj, bean);
            }
            // 2.2 如果没有再按照类型查找
            else {
                bean = beans.get(f.getType().getName());
                // 2.2.1 找到就注入Bean
                if (bean != null) {
                    ReflectUtils.FieldSetter(f, obj, bean);
                }
                // 2.2.2 如果没找到就按照接口去定义对象里面匹配实现类
                else {
                    // 2.1 先从容器里面去取
                    bean = beans.get(findInterface(defines, f,  beans));
                    // 2.2 容器中没找到
                    if (bean == null) throw new RuntimeException("[ERR @di] Autowired Failed {" + f.getName() + "} NOT FOUND");
                    // 2.3 找到就注入Bean
                    ReflectUtils.FieldSetter(f, obj, bean);

                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException
                    ("[ERR @di @" + obj.getClass().getName() + "] Resource Failed, " +
                            "{" + f.getType().getName() + "} NOT FOUND");
        }
    }


    /**
     * 设置属性值
     * @param obj
     */
    public static void setValue(Object obj, Field f) {
        // 进行属性注入
        String valueStr = f.getAnnotation(Value.class).value();
        try {
            // 值为空就跳过
            if (valueStr == null) return;

            // 处理int/Integer数组类型
            if (f.getType().isArray() && (
                    f.getType().getComponentType() == int.class
                            || f.getType().getComponentType() == Integer.class)) {
                // 将元素分割为字符串数组
                String[] parts = valueStr.split(",");
                // 声明要赋的值
                Object valueArray = null;

                // int数组
                if (f.getType().getComponentType() == int.class) {
                    int[] intArray = new int[parts.length];
                    for (int i = 0; i < parts.length; i++)
                        intArray[i] = Integer.parseInt(parts[i].trim());
                    valueArray = intArray;
                }
                // Integer数组
                else {
                    Integer[] intArray = new Integer[parts.length];
                    for (int i = 0; i < parts.length; i++)
                        intArray[i] = Integer.parseInt(parts[i].trim());
                    valueArray = intArray;
                }
                // 赋值
                ReflectUtils.FieldSetter(f, obj, valueArray);

                // 处理List
            } else if (f.getType() == List.class) {
                // 赋值
                ReflectUtils.FieldSetter(f, obj, Arrays.asList(valueStr.split(",")));
            } else {
                // 其他类型使用BeanUtils
                BeanUtils.copyProperty(obj, f.getName(), valueStr);
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("[ERR @di] 处理对象 {" + obj + "} 时失败...");
        }
    }


    /**
     * 从定义对象列表中匹配接口实现类
     * @param defines 定义对象列表
     * @param f 需要注入的字段
     * @return 匹配的BeanKey
     */
    public static String findInterface(List<BeanDefine> defines, Field f, Map beans) {
        // 用于注入的列表, 因为一个接口可能有多个实现类
        List<String> beanKeys = new ArrayList<>();
        // 遍历定义对象列表
        for (BeanDefine define : defines) {
            // 遍历对象的接口
            for (Class<?> interfaceClass : define.getInterfaces()) {
                // 如果匹配接口, 则加入注入列表中
                if (interfaceClass.getName().equals(f.getType().getName())) {
                    if (beans.get(define.getContextKey()) != null) {
                        // 检测有无Primary注解标识注入类
                        if (define.getClazz().isAnnotationPresent(Primary.class))
                            // 如果被Primary修饰就立刻返回这个注入类的Key
                            return define.getContextKey();

                        // 如果没有被Primary修饰, 则加入注入列表中
                        beanKeys.add(define.getContextKey());
                    }
                }
            }
        }
        // 一个都没匹配到
        if (beanKeys.isEmpty())
            return null;
        // 匹配到好多个
        if (beanKeys.size() > 1)
            throw new RuntimeException("[ERR @di] 存在多个匹配接口 {" + f.getType().getName() + "} 的对象...");

        // 正常情况, 只有一个匹配到了的
        return beanKeys.get(0);
    }


}
